package database

import (
	"gitee.com/easy_code/go-redis/aof"
	"gitee.com/easy_code/go-redis/config"
	"gitee.com/easy_code/go-redis/interface/resp"
	"gitee.com/easy_code/go-redis/lib/logger"
	"gitee.com/easy_code/go-redis/resp/reply"
	"strconv"
	"strings"
)

type StandaloneDatabase struct {
	dbSet      []*DB
	AofHandler *aof.AofHandler // 全部 db 共用一个 aof 处理，只需要记录 select db 命令即可
}

func NewStandaloneDatabase() *StandaloneDatabase {
	mdb := &StandaloneDatabase{}
	// redis 数据库个数
	if config.Properties.Databases == 0 {
		config.Properties.Databases = 16
	}
	// 初始化 db
	mdb.dbSet = make([]*DB, config.Properties.Databases)
	for i := range mdb.dbSet {
		db := makeDB()
		db.index = i
		mdb.dbSet[i] = db
	}
	if config.Properties.AppendOnly {
		aofHandler, err := aof.NewAofHandler(mdb)
		if err != nil {
			panic(err)
		}
		mdb.AofHandler = aofHandler
		for _, db := range mdb.dbSet {
			// 不用闭包， db.index 拿的是 for 循环结束后的值
			// 原理：闭包引用外部变量 db，此时 db 变量逃逸到堆上。因为闭包读取的是堆上最新的值
			sdb := db // 由于 sdb 是在 for 循环里面重新赋值，因此 sdb 的值会不断改变
			sdb.addAof = func(line CmdLine) {
				//mdb.AofHandler.AddAof(db.index, line)
				mdb.AofHandler.AddAof(sdb.index, line)
			}
		}
	}
	return mdb
}

func (database *StandaloneDatabase) Exec(client resp.Connection, args [][]byte) resp.Reply {
	// 由于 Exec 是整个 redis 的核心功能，因此需要捕获painc错误，防止程序中断
	defer func() {
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	// set k v
	// get k
	// select 2
	cmdName := strings.ToLower(string(args[0]))
	if cmdName == "select" {
		if len(args) != 2 {
			return reply.MakeArgNumErrReply("select")
		}
		return execSelect(client, database, args[1:])
	}
	dbIndex := client.GetDBIndex()
	db := database.dbSet[dbIndex]
	return db.Exec(client, args)
}

func (database *StandaloneDatabase) Close() {

}

func (database *StandaloneDatabase) AfterClientClose(c resp.Connection) {

}

// execSelect 切换redis数据库。如：select 2（args 不包含 select）
func execSelect(c resp.Connection, database *StandaloneDatabase, args [][]byte) resp.Reply {
	dbIndex, err := strconv.Atoi(string(args[0]))
	// 不是数字
	if err != nil {
		return reply.MakeErrReply("ERR invalid DB index")
	}
	// 大于设置的数据库数量
	if dbIndex >= len(database.dbSet) {
		return reply.MakeErrReply("ERR DB index is out of range")
	}
	c.SelectDB(dbIndex)
	return reply.MakeOkReply()
}
